
#include "convertutf.h"

#define UTF8_LENGTH(Char)              \
  ((Char) < 0x80 ? 1 :                 \
   ((Char) < 0x800 ? 2 :               \
    ((Char) < 0x10000 ? 3 :            \
     ((Char) < 0x200000 ? 4 :          \
      ((Char) < 0x4000000 ? 5 : 6)))))

   /*
   * Check whether a Unicode (5.2) char is in a valid range.
   *
   * The first check comes from the Unicode guarantee to never encode
   * a point above 0x0010ffff, since UTF-16 couldn't represent it.
   *
   * The second check covers surrogate pairs (category Cs).
   *
   * @param Char the character
   */
#define UNICODE_VALID(Char)                   \
    ((Char) < 0x110000 &&                     \
     (((Char) & 0xFFFFF800) != 0xD800))

#define SURROGATE_VALUE(h,l) (((h) - 0xd800) * 0x400 + (l) - 0xdc00 + 0x10000)

/**
 * g_unichar_to_utf8:
 * @c: a Unicode character code
 * @outbuf: output buffer, must have at least 6 bytes of space.
 *       If %NULL, the length will be computed and returned
 *       and nothing will be written to @outbuf.
 *
 * Converts a single character to UTF-8.
 *
 * Return value: number of bytes written
 **/
int
g_unichar_to_utf8(utf32_t c, char* outbuf)
{
	/* If this gets modified, also update the copy in g_string_insert_unichar() */
	int len = 0;
	int first;

	if (c < 0x80) {
		first = 0;
		len = 1;
	}
	else if (c < 0x800) {
		first = 0xc0;
		len = 2;
	}
	else if (c < 0x10000) {
		first = 0xe0;
		len = 3;
	}
	else if (c < 0x200000) {
		first = 0xf0;
		len = 4;
	}
	else if (c < 0x4000000) {
		first = 0xf8;
		len = 5;
	}
	else {
		first = 0xfc;
		len = 6;
	}

	if (outbuf) {
		for (int i = len - 1; i > 0; --i) {
			outbuf[i] = (c & 0x3f) | 0x80;
			c >>= 6;
		}
		outbuf[0] = (char)(c | first);
	}

	return len;
}

/**
* g_utf8_get_char:
* @p: a pointer to Unicode character encoded as UTF-8
*
* Converts a sequence of bytes encoded as UTF-8 to a Unicode character.
* If @p does not point to a valid UTF-8 encoded character, results are
* undefined. If you are not sure that the bytes are complete
* valid Unicode characters, you should use g_utf8_get_char_validated()
* instead.
*
* Return value: the resulting character
**/
utf32_t
g_utf8_get_char(const char *p)
{
	int mask = 0;
	int len;
	unsigned char ch = (unsigned char)(*p);

	if (ch < 128) {
		len = 1;
		mask = 0x7f;
	}      
	else if ((ch & 0xe0) == 0xc0) {
		len = 2;
		mask = 0x1f;
	}
	else if ((ch & 0xf0) == 0xe0) {
		len = 3;
		mask = 0x0f;
	}
	else if ((ch & 0xf8) == 0xf0) {
		len = 4;
		mask = 0x07;
	}
	else if ((ch & 0xfc) == 0xf8) {
		len = 5;
		mask = 0x03;
	}
	else if ((ch & 0xfe) == 0xfc) {
		len = 6;
		mask = 0x01;
	}
	else {
		return (utf32_t)-1;
	}
	
	const char* chars = p;
	
	utf32_t result = chars[0] & mask;
	for (int i = 1; i < len; ++i) {
		if (((chars)[i] & 0xc0) != 0x80) {
			result = (utf32_t)-1;
			break;
		}
		result <<= 6;
		result |= (chars[i] & 0x3f);
	}
	return result;
}

/* Like g_utf8_get_char, but take a maximum length
* and return (utf32_t)-2 on incomplete trailing character;
* also check for malformed or overlong sequences
* and return (utf32_t)-1 in this case.
*/
static inline utf32_t
g_utf8_get_char_extended(const char *p, long max_len)
{
	long len;
	utf32_t min_code;
	utf32_t wc = (unsigned char)*p;

	if (wc < 0x80) {
		return wc;
	}
	else if (wc < 0xc0) {
		return (utf32_t)-1;
	}
	else if (wc < 0xe0) {
		len = 2;
		wc &= 0x1f;
		min_code = 1 << 7;
	}
	else if (wc < 0xf0) {
		len = 3;
		wc &= 0x0f;
		min_code = 1 << 11;
	}
	else if (wc < 0xf8) {
		len = 4;
		wc &= 0x07;
		min_code = 1 << 16;
	}
	else if (wc < 0xfc) {
		len = 5;
		wc &= 0x03;
		min_code = 1 << 21;
	}
	else if (wc < 0xfe) {
		len = 6;
		wc &= 0x01;
		min_code = 1 << 26;
	}
	else {
		return (utf32_t)-1;
	}

	if (max_len >= 0 && len > max_len) {
		for (int i = 1; i < max_len; i++) {
			if ((((unsigned char *)p)[i] & 0xc0) != 0x80)
				return (utf32_t)-1;
		}
		return (utf32_t)-2;
	}

	for (int i = 1; i < len; ++i) {
		utf32_t ch = ((unsigned char *)p)[i];

		if ((ch & 0xc0) != 0x80) {
			if (ch) {
				return (utf32_t)-1;
			}
			else {
				return (utf32_t)-2;
			}
		}

		wc <<= 6;
		wc |= (ch & 0x3f);
	}

	if (wc < min_code) {
		return (utf32_t)-1;
	}

	return wc;
}

static const char utf8_skip_data[256] = {
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
	3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};

const char* const g_utf8_skip = utf8_skip_data;

/**
* g_utf8_next_char:
* @p: Pointer to the start of a valid UTF-8 character
*
* Skips to the next character in a UTF-8 string. The string must be
* valid; this macro is as fast as possible, and has no error-checking.
* You would use this macro to iterate over a string character by
* character. The macro returns the start of the next UTF-8 character.
* Before using this macro, use g_utf8_validate() to validate strings
* that may contain invalid UTF-8.
*/
#define g_utf8_next_char(p) (char*)((p) + g_utf8_skip[*(const unsigned char*)(p)])

char*
g_ucs4_to_utf8(const utf32_t* str, long len, long* items_read, long* items_written)
{
	if (str == NULL) {
		return NULL;
	}
	char* result = NULL;
	int result_length = 0;
	int i = 0;
	for (; len < 0 || i < len; i++) {
		if (!str[i]) {
			break;
		}
		if (str[i] >= 0x80000000) {
			goto err_out;
		}

		result_length += UTF8_LENGTH(str[i]);
	}

	result = (char*)malloc((result_length + 1) * sizeof(char));
	if (result == 0) {
		goto err_out;
	}
	char* p = result;

	i = 0;
	while (p < result + result_length) {
		p += g_unichar_to_utf8(str[i++], p);
	}
	*p = '\0';

	if (items_written) {
		*items_written = p - result;
	}

err_out:
	if (items_read) {
		*items_read = i;
	}
	return result;
}

utf32_t*
g_utf8_to_ucs4(const char* str, long len, long* items_read, long* items_written)
{
	if (str == NULL) {
		return NULL;
	}
	utf32_t* result = NULL;
	int n_chars = 0;
	const char* in = str;
	while ((len < 0 || str + len - in > 0) && *in) {
		utf32_t wc = g_utf8_get_char_extended(in, len < 0 ? 6 : str + len - in);
		if (wc & 0x80000000) {
			if (wc == (utf32_t)-2) {
				if (items_read)
					break;
			}
			goto err_out;
		}

		n_chars++;

		in = g_utf8_next_char(in);
	}

	result = (utf32_t*)malloc((n_chars + 1) * sizeof(utf32_t));
	if (result == NULL) {
		goto err_out;
	}

	in = str;
	int i = 0;
	for (; i < n_chars; i++) {
		result[i] = g_utf8_get_char(in);
		in = g_utf8_next_char(in);
	}
	result[i] = 0;

	if (items_written) {
		*items_written = n_chars;
	}

err_out:
	if (items_read) {
		*items_read = in - str;
	}

	return result;
}

char*
g_utf16_to_utf8(const utf16_t* str, long len, long* items_read, long* items_written)
{
	/* This function and g_utf16_to_ucs4 are almost exactly identical - The lines that differ
	* are marked.
	*/
	if (str == NULL) {
		return NULL;
	}
	char* result = NULL;
	const utf16_t* in = str;
	int n_bytes = 0;
	utf32_t high_surrogate = 0;

	while ((len < 0 || in - str < len) && *in) {
		utf16_t c = *in;
		utf32_t wc;

		if (c >= 0xdc00 && c < 0xe000) { /* low surrogate */
			if (high_surrogate) {
				wc = SURROGATE_VALUE(high_surrogate, c);
				high_surrogate = 0;
			}
			else {
				goto err_out;
			}
		}
		else {
			if (high_surrogate) {
				goto err_out;
			}

			if (c >= 0xd800 && c < 0xdc00) { /* high surrogate */
				high_surrogate = c;
				goto next1;
			}
			else {
				wc = c;
			}
		}

		/********** DIFFERENT for utf8_t/UCS4 **********/
		n_bytes += UTF8_LENGTH(wc);

	next1:
		in++;
	}

	if (high_surrogate && !items_read) {
		goto err_out;
	}

	/* At this point, everything is valid, and we just need to convert
	*/
	/********** DIFFERENT for utf8_t/UCS4 **********/
	result = (char*)malloc((n_bytes + 1) * sizeof (char));
	if (result == NULL) {
		goto err_out;
	}

	high_surrogate = 0;
	char* out = result;
	in = str;
	while (out < result + n_bytes) {
		utf16_t c = *in;
		utf32_t wc;

		if (c >= 0xdc00 && c < 0xe000) { /* low surrogate */
			wc = SURROGATE_VALUE(high_surrogate, c);
			high_surrogate = 0;
		}
		else if (c >= 0xd800 && c < 0xdc00) { /* high surrogate */
			high_surrogate = c;
			goto next2;
		}
		else {
			wc = c;
		}

		/********** DIFFERENT for utf8_t/UCS4 **********/
		out += g_unichar_to_utf8(wc, out);

	next2:
		in++;
	}

	/********** DIFFERENT for utf8_t/UCS4 **********/
	*out = '\0';

	if (items_written) {
		/********** DIFFERENT for utf8_t/UCS4 **********/
		*items_written = out - result;
	}

err_out:
	if (items_read) {
		*items_read = in - str;
	}

	return result;
}

utf16_t*
g_utf8_to_utf16(const char *str, long len, long* items_read, long* items_written)
{
	if (str == NULL) {
		return NULL;
	}

	utf16_t* result = NULL;
	int n16 = 0;
	const char *in = str;
	int i;

	while ((len < 0 || str + len - in > 0) && *in) {
		utf32_t wc = g_utf8_get_char_extended(in, len < 0 ? 6 : str + len - in);
		if (wc & 0x80000000) {
			if (wc == (utf32_t)-2) {
				if (items_read)
					break;
			}
			goto err_out;
		}

		if (wc < 0xd800) {
			n16 += 1;
		}
		else if (wc < 0xe000) {
			goto err_out;
		}
		else if (wc < 0x10000) {
			n16 += 1;
		}
		else if (wc < 0x110000) {
			n16 += 2;
		}
		else {
			goto err_out;
		}

		in = g_utf8_next_char(in);
	}

	result = (utf16_t*)malloc((n16 + 1) * sizeof(utf16_t));
	if (result == NULL) {
		goto err_out;
	}

	in = str;
	for (i = 0; i < n16;) {
		utf32_t wc = g_utf8_get_char(in);

		if (wc < 0x10000) {
			result[i++] = (utf16_t)wc;
		}
		else {
			result[i++] = (utf16_t)((wc - 0x10000) / 0x400 + 0xd800);
			result[i++] = (utf16_t)((wc - 0x10000) % 0x400 + 0xdc00);
		}

		in = g_utf8_next_char(in);
	}

	result[i] = 0;

	if (items_written) {
		*items_written = n16;
	}

err_out:
	if (items_read) {
		*items_read = in - str;
	}

	return result;
}

utf32_t*
g_utf16_to_ucs4(const utf16_t* str, long len, long* items_read, long* items_written)
{
	if (str == NULL) {
		return NULL;
	}
	char* result = NULL;
	const utf16_t* in = str;
	int n_bytes = 0;
	utf32_t high_surrogate = 0;

	while ((len < 0 || in - str < len) && *in) {
		utf16_t c = *in;

		if (c >= 0xdc00 && c < 0xe000) { /* low surrogate */
			if (high_surrogate) {
				high_surrogate = 0;
			}
			else {
				goto err_out;
			}
		}
		else {
			if (high_surrogate) {
				goto err_out;
			}

			if (c >= 0xd800 && c < 0xdc00) { /* high surrogate */
				high_surrogate = c;
				goto next1;
			}
		}

		/********** DIFFERENT for utf8_t/UCS4 **********/
		n_bytes += sizeof(utf32_t);

	next1:
		in++;
	}

	if (high_surrogate && !items_read) {
		goto err_out;
	}

	/* At this point, everything is valid, and we just need to convert
	*/
	/********** DIFFERENT for utf8_t/UCS4 **********/
	result = (char*)malloc((n_bytes + 4) * sizeof(char));
	if (result == NULL) {
		goto err_out;
	}

	high_surrogate = 0;
	char* out = result;
	in = str;
	while (out < result + n_bytes) {
		utf16_t c = *in;
		utf32_t wc;

		if (c >= 0xdc00 && c < 0xe000) { /* low surrogate */
			wc = SURROGATE_VALUE(high_surrogate, c);
			high_surrogate = 0;
		}
		else if (c >= 0xd800 && c < 0xdc00) { /* high surrogate */
			high_surrogate = c;
			goto next2;
		}
		else {
			wc = c;
		}

		/********** DIFFERENT for utf8_t/UCS4 **********/
		*(utf32_t *)out = wc;
		out += sizeof(utf32_t);

	next2:
		in++;
	}

	/********** DIFFERENT for utf8_t/UCS4 **********/
	*(utf32_t *)out = 0;

	if (items_written) {
		/********** DIFFERENT for utf8_t/UCS4 **********/
		*items_written = (out - result) / sizeof(utf32_t);
	}

err_out:
	if (items_read) {
		*items_read = in - str;
	}

	return (utf32_t*)result;
}

utf16_t*
g_ucs4_to_utf16(const utf32_t* str, long len, long* items_read, long* items_written)
{
	if (str == NULL) {
		return NULL;
	}

	utf16_t* result = NULL;
	int n16 = 0;
	int i = 0;
	while ((len < 0 || i < len) && str[i]) {
		utf32_t wc = str[i];

		if (wc < 0xd800) {
			n16 += 1;
		}
		else if (wc < 0xe000) {
			goto err_out;
		}
		else if (wc < 0x10000) {
			n16 += 1;
		}
		else if (wc < 0x110000) {
			n16 += 2;
		}
		else {
			goto err_out;
		}

		i++;
	}

	result = (utf16_t*)malloc((n16 + 1) * sizeof(utf16_t));
	if (result == NULL) {
		goto err_out;
	}

	int j = 0;
	for (i = 0; j < n16; i++) {
		utf32_t wc = str[i];

		if (wc < 0x10000) {
			result[j++] = (utf16_t)wc;
		}
		else {
			result[j++] = (utf16_t)((wc - 0x10000) / 0x400 + 0xd800);
			result[j++] = (utf16_t)((wc - 0x10000) % 0x400 + 0xdc00);
		}
	}
	result[j] = 0;

	if (items_written) {
		*items_written = n16;
	}

err_out:
	if (items_read) {
		*items_read = i;
	}

	return result;
}
